/*
 * Implementation of decoding response in CBOR format
 *
 * Copyright (c) Huawei Technologies Co., Ltd. 2022. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */
#include <string.h>
#include "qtsm_cbor_msg.h"

static bool cbor_res_get_uint32(const cbor_item_t *root,
    size_t index, uint32_t *objp)
{
    cbor_item_t *cur = NULL;
    bool rc;

    cur = cbor_array_get(root, index);
    if (!cur)
        return false;

    rc = cbor_isa_uint(cur);
    if (!rc)
        goto err;

    if (cbor_int_get_width(cur) != CBOR_INT_32) {
        rc = false;
        goto err;
    }

    *objp = cbor_get_uint32(cur);

err:
    cbor_decref(&cur);
    return rc;
}

static bool cbor_res_get_uint16(const cbor_item_t *root,
    size_t index, uint16_t *objp)
{
    cbor_item_t *cur = NULL;
    bool rc;

    cur = cbor_array_get(root, index);
    if (!cur)
        return false;

    rc = cbor_isa_uint(cur);
    if (!rc)
        goto err;

    if (cbor_int_get_width(cur) != CBOR_INT_16) {
        rc = false;
        goto err;
    }

    *objp = cbor_get_uint16(cur);

err:
    cbor_decref(&cur);
    return rc;
}

static bool cbor_res_get_bool(const cbor_item_t *root,
    size_t index, bool *objp)
{
    cbor_item_t *cur = NULL;
    bool rc;

    cur = cbor_array_get(root, index);
    if (!cur)
        return false;

    rc = cbor_is_bool(cur);
    if (!rc)
        goto err;

    *objp = cbor_get_bool(cur);

err:
    cbor_decref(&cur);
    return rc;
}

static bool cbor_res_get_bytestring(const cbor_item_t *root, size_t index,
                                    uint8_t **objp, uint32_t *objp_len)
{
    cbor_item_t *cur = NULL;
    unsigned char *cur_str = NULL;
    bool rc;

    cur = cbor_array_get(root, index);
    if (!cur)
        return false;

    rc = cbor_isa_bytestring(cur);
    if (!rc)
        goto err;

    cur_str = cbor_bytestring_handle(cur);
    if (!cur_str) {
        rc = false;
        goto err;
    }
    *objp_len = cbor_bytestring_length(cur);

    *objp = malloc(*objp_len);
    if (!(*objp)) {
        rc = false;
        goto err;
    }

    memcpy(*objp, cur_str, *objp_len);

err:
    cbor_decref(&cur);
    return rc;
}

static bool cbor_res_get_string(const cbor_item_t *root, size_t index,
    cbor_mutable_data *objp, uint32_t *objp_len)
{
    cbor_item_t *cur = NULL;
    bool rc;

    cur = cbor_array_get(root, index);
    if (!cur)
        return false;

    rc = cbor_isa_string(cur);
    if (!rc)
        goto err;

    *objp = cbor_string_handle(cur);
    if (!(*objp)) {
        rc = false;
        goto err;
    }
    *objp_len = cbor_string_length(cur);

err:
    cbor_decref(&cur);
    return rc;
}

static bool cbor_res_get_uint16_array(const cbor_item_t *root, size_t index,
                                      uint32_t *num, uint16_t *objp)
{
    cbor_item_t *cur = NULL;
    size_t i;
    bool rc;

    cur = cbor_array_get(root, index);
    if (!cur)
        return false;

    rc = cbor_isa_array(cur);
    if (!rc)
        goto err;

    *num = cbor_array_size(cur);
    if (*num > QTSM_MAX_PCR_COUNT) {
        rc = false;
        goto err;
    }
    for (i = 0; i < *num; i++) {
        rc = cbor_res_get_uint16(cur, i, objp + i);
        if (!rc)
            goto err;
    }

err:
    cbor_decref(&cur);
    return rc;
}

static bool cbor_res_type(const cbor_item_t *root, req_type *objp)
{
    size_t num;
    bool rc;

    /* check if array */
    rc = cbor_isa_array(root);
    if (!rc)
        return false;

    num = cbor_array_size(root);
    if (num <= 1)
        return false;

    /* type */
    return cbor_res_get_uint32(root, 0, objp);
}

static bool cbor_res_describe_pcr(const cbor_item_t *root,
                                  res_describe_pcr *objp)
{
    size_t index = 0;
    bool rc;

    /* lock */
    index++;
    rc = cbor_res_get_bool(root, index, &(objp->locked));
    if (!rc)
        return false;

    /* data */
    index++;
    return cbor_res_get_bytestring(root, index, &(objp->data), &(objp->data_len));
}

static bool cbor_res_extend_pcr(const cbor_item_t *root,
                                res_extend_pcr *objp)
{
    /* data */
    return cbor_res_get_bytestring(root, 1, &(objp->data), &(objp->data_len));
}

static bool cbor_res_lock_pcr(const cbor_item_t *root,
                              res_lock_pcr *objp)
{
    /* lock */
    return cbor_res_get_bool(root, 1, &(objp->locked));
}

static bool cbor_res_lock_pcrs(const cbor_item_t *root,
    res_lock_pcrs *objp)
{
    /* lock */
    return cbor_res_get_bool(root, 1, &(objp->locked));
}

static bool cbor_res_describe_qtsm(const cbor_item_t *root,
                                   describe_qtsm *objp)
{
    size_t index = 0;
    cbor_mutable_data cur_str = NULL;
    bool rc;

    /* version_major */
    index++;
    rc = cbor_res_get_uint16(root, index, &(objp->version_major));
    if (!rc)
        goto err;

    /* version_minor */
    index++;
    rc = cbor_res_get_uint16(root, index, &(objp->version_minor));
    if (!rc)
        goto err;

    /* version_patch */
    index++;
    rc = cbor_res_get_uint16(root, index, &(objp->version_patch));
    if (!rc)
        goto err;

    /* module_id */
    index++;
    rc = cbor_res_get_string(root, index, &cur_str, &(objp->module_id_len));
    if (!rc || objp->module_id_len >= QTSM_MODULE_ID_MAX_SIZE) {
        rc = false;
        goto err;
    }

    memset(objp->module_id, 0, QTSM_MODULE_ID_MAX_SIZE);
    memcpy(objp->module_id, cur_str, objp->module_id_len);

    /* max_pcrs */
    index++;
    rc = cbor_res_get_uint16(root, index, &(objp->max_pcrs));
    if (!rc)
        goto err;

    /* locked_pcrs_num + locked_pcrs */
    index++;
    rc = cbor_res_get_uint16_array(root, index, &(objp->locked_pcrs_num),
        objp->locked_pcrs);
    if (!rc)
        goto err;

    /* digest */
    index++;
    rc = cbor_res_get_uint32(root, index, &(objp->digest));

err:
    return rc;
}

static bool cbor_res_attestation(const cbor_item_t *root,
                                 res_attestation *objp)
{
    /* data */
    return cbor_res_get_bytestring(root, 1, &(objp->data), &(objp->data_len));
}

static bool cbor_res_error_code(const cbor_item_t *root,
                                enum error_code *objp)
{
    /* error code */
    return cbor_res_get_uint32(root, 1, objp);
}

bool cbor_qtsm_message_res(const cbor_item_t *root, qtsm_message_res *objp)
{
    if (!cbor_res_type(root, &objp->type))
        return false;

    switch (objp->type) {
        case DESCRIBE_PCR:
            if (!cbor_res_describe_pcr(root, &objp->qtsm_message_res_u.describe_pcr))
                return false;
            break;
        case EXTEND_PCR:
            if (!cbor_res_extend_pcr(root, &objp->qtsm_message_res_u.extend_pcr))
                return false;
            break;
        case LOCK_PCR:
            if (!cbor_res_lock_pcr(root, &objp->qtsm_message_res_u.lock_pcr))
                return false;
            break;
        case LOCK_PCRS:
            if (!cbor_res_lock_pcrs(root, &objp->qtsm_message_res_u.lock_pcrs))
                return false;
            break;
        case DESCRIBE_QTSM:
            if (!cbor_res_describe_qtsm(root, &objp->qtsm_message_res_u.describe_qtsm))
                return false;
            break;
        case ATTESTATION:
            if (!cbor_res_attestation(root, &objp->qtsm_message_res_u.attestation))
                return false;
            break;
        case ERROR_CODE:
            if (!cbor_res_error_code(root, &objp->qtsm_message_res_u.error_num))
                return false;
            break;
        default:
            return false;
    }

    return true;
}
